﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Threading;

namespace AMVInfoSpace
{

    public class GrabInfo
    {
        public SITE Site;
        public string Html;
        public readonly int AmvID;
        public AMVInfo AmvInfo = null;
        public Exception LoadEx = null;
        public Exception ParseEx = null;
        public bool PageIsCorrect = false;
        /// <summary>Ошибка получения HTML или парсинга</summary>
        public bool IsGrabError { get { return ParseEx != null || LoadEx != null; } }

        public GrabInfo(SITE Site, int AmvID)
        {
            this.Site = Site;
            this.AmvID = AmvID;
        }
    }


    public class Grab : IDisposable
    {

        class GrabOneList : IDisposable
        {
            int actInd = 0;
            Thread th = null;
            public List<GrabInfo> grList = new List<GrabInfo>();

            public bool IsGrabAll { get { return actInd == grList.Count; } }
            public int DoneCount { get { return actInd; } }

            /// <summary>Удалять html для удачно распарсеных страниц (рекомендуется для сохранения памяти)</summary>
            public bool DeleteGoodPadesAfterParse = true;
            /// <summary>Удалять html всех страниц после парсинга</summary>
            public bool DeleteAllPadesAfterParse = false;

            bool UseWebBrouser = false;

            /// <summary>Необходимо вызывать в каждом цикле</summary>
            public void Action()
            {
                if (IsGrabAll) return;

                if (thState == ExecuteState.None)
                {
                    if (UseWebBrouser)
                    {
                        th = new Thread(new ThreadStart(ExecuteInThreadWB));
                        th.SetApartmentState(ApartmentState.STA);
                    }
                    else th = new Thread(new ThreadStart(ExecuteInThread));
                    th.IsBackground = true;
                    thState = ExecuteState.LoadAndParseHtml;
                    th.Start();
                    return;
                }

                if (thState == ExecuteState.EndLoadAndParseHtml || thState == ExecuteState.Error)
                {
                    th = null;
                    actInd++;
                    thState = ExecuteState.None;
                }
            }

            enum ExecuteState { None, LoadAndParseHtml, EndLoadAndParseHtml, Error }
            ExecuteState thState = ExecuteState.None;

            //WebBrowser wbr = null;

            public GrabOneList(bool UseWebBrouser)
            {
                if (UseWebBrouser)
                {
                    /*wbr = new WebBrowser();
                    wbr.MinimumSize = new System.Drawing.Size(20, 20);
                    wbr.Name = "wbr";
                    wbr.Size = new System.Drawing.Size(764, 389);
                    wbr.ScriptErrorsSuppressed = true;*/
                }
                this.UseWebBrouser = UseWebBrouser;
            }

            void ExecuteInThread()
            {
                try
                {
                    try
                    {
                        using (WebClient wClient = new WebClient())
                        {
                            wClient.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
                            wClient.Proxy = new System.Net.WebProxy();

                            string url = "";
                            Encoding enc = Encoding.GetEncoding("windows-1251");
                            if (grList[actInd].Site == SITE.Akross) enc = Encoding.UTF8;
                            if (!TryGetUrl(out url)) return;

                            using (Stream data = wClient.OpenRead(url))
                            using (StreamReader reader = new StreamReader(data, enc))
                            {
                                grList[actInd].Html = reader.ReadToEnd();
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        grList[actInd].LoadEx = new Exception("Не удалось загрузить страницу", ex);
                        thState = ExecuteState.Error;
                        return;
                    }

                    ParseActElement();
                    thState = ExecuteState.EndLoadAndParseHtml;
                }
                catch
                {
                    //MessageBox.Show("Ошибка выполнения потока: " + ex.Message + "\r\nТрассировка:" + ex.StackTrace); thState = ExecuteState.EndLoadAndParseHtml; 
                }
            }

            object objLock = new object();

            void ExecuteInThreadWB()
            {
                try
                {
                    string url = "";
                    if (!TryGetUrl(out url)) return;

                    DateTime dtTimeout = DateTime.Now.AddSeconds(30);
                    string html = "";

                    using (WebBrowser wbr = new WebBrowser())
                    {
                        wbr.MinimumSize = new System.Drawing.Size(20, 20);
                        wbr.Name = "wbr";
                        wbr.Size = new System.Drawing.Size(764, 389);
                        wbr.ScriptErrorsSuppressed = true;
                        wbr.Navigate(url);
                        while (dtTimeout > DateTime.Now)
                        {
                            if (wbr.ReadyState == WebBrowserReadyState.Complete)
                            {
                                String encodingAS = wbr.Document.Encoding;
                                Encoding hDocEncoding = Encoding.GetEncoding(encodingAS);
                                using (System.IO.Stream aStream = wbr.DocumentStream)
                                using (System.IO.StreamReader SR = new System.IO.StreamReader(aStream, hDocEncoding))
                                {
                                    String aText = "";
                                    while (SR.Peek() >= 0)
                                        aText += SR.ReadLine() + "\r\n";
                                    html = aText;
                                }
                                break;
                            }
                            Application.DoEvents();
                        }
                    }
                    if (html == "")
                    {
                        grList[actInd].LoadEx = new Exception("Не удалось загрузить страницу. Timeout (30 секунд)", null);
                        thState = ExecuteState.Error;
                        return;
                    }
                    grList[actInd].Html = html;

                    ParseActElement();
                    thState = ExecuteState.EndLoadAndParseHtml;
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Ошибка выполнения потока: " + ex.Message + "\r\nТрассировка:" + ex.StackTrace); thState = ExecuteState.EndLoadAndParseHtml;
                }
            }

            bool bComplite = false;
            void Wbr_Complite(object sender, WebBrowserDocumentCompletedEventArgs e)
            {

            }

            bool TryGetUrl(out string Url)
            {
                switch (grList[actInd].Site)
                {
                    case SITE.AMVNews:
                        {
                            Url = "http://amvnews.ru/index.php?go=Files&in=view&id=" + grList[actInd].AmvID.ToString();
                            return true;
                        }
                    case SITE.Akross:
                        {
                            Url = "http://www.akross.ru/index.cgi?act=video;id=" + grList[actInd].AmvID.ToString();
                            return true;
                        }
                    case SITE.CreaCon:
                        {
                            Url = "http://creaspace.ru/videos/video.php?video_id=" + grList[actInd].AmvID.ToString();
                            return true;
                        }
                    default:
                        {
                            grList[actInd].LoadEx = new Exception("Сайт " + grList[actInd].Site.ToString() + " не поддерживается");
                            thState = ExecuteState.Error;
                            Url = "";
                            return false;
                        }
                }
            }

            void ParseActElement()
            {
                // проверка корректности страницы
                bool pageIsCorrect = false;
                switch (grList[actInd].Site)
                {
                    case SITE.AMVNews: pageIsCorrect = AmvHtmlParser.PageAMVNews_IsCorrect(ref grList[actInd].Html); break;
                    case SITE.Akross: pageIsCorrect = AmvHtmlParser.PageAkross_IsCorrect(ref grList[actInd].Html); break;
                    case SITE.CreaCon: pageIsCorrect = AmvHtmlParser.PageCreaCon_IsCorrect(ref grList[actInd].Html, grList[actInd].AmvID); break;
                }
                grList[actInd].PageIsCorrect = pageIsCorrect;

                // парсинг
                if (pageIsCorrect)
                {
                    grList[actInd].PageIsCorrect = true;
                    try
                    {
                        switch (grList[actInd].Site)
                        {
                            case SITE.AMVNews: grList[actInd].AmvInfo = AmvHtmlParser.ParseAMVNewsInfo(grList[actInd].Html, grList[actInd].AmvID); break;
                            case SITE.Akross: grList[actInd].AmvInfo = AmvHtmlParser.ParseAkrossInfo(grList[actInd].Html, grList[actInd].AmvID); break;
                            case SITE.CreaCon: grList[actInd].AmvInfo = AmvHtmlParser.ParseCreaConInfo(grList[actInd].Html, grList[actInd].AmvID); break;
                        }
                        if (DeleteGoodPadesAfterParse) grList[actInd].Html = "";
                    }
                    catch (Exception ex)
                    {
                        grList[actInd].ParseEx = ex;
                    }
                }

                if (DeleteAllPadesAfterParse) grList[actInd].Html = "";
            }

            ~GrabOneList() { Dispose(); }

            public void Dispose()
            {
                if (th != null)
                {
                    th.Abort();
                    th = null;
                }
            }
        }

        System.Windows.Forms.Timer tmr = null;
        List<GrabOneList> grabList = null;
        int StartInd;
        int IndLength;
        SITE Site;
        List<int> AmvIdList = null;
        const int maxThriadsCount = 8;

        bool UseWebBrouser = false;

        int doneCount = 0;
        /// <summary>Количество сграбленых страниц</summary>
        public int DoneCount { get { return doneCount; } }
        bool done = false;
        public bool Done { get { return done; } }

        /// <summary>Удалять html для удачно распарсеных страниц (рекомендуется для сохранения памяти)</summary>
        public bool DeleteGoodPadesAfterParse = true;
        /// <summary>Удалять html всех страниц после парсинга</summary>
        public bool DeleteAllPadesAfterParse = false;

        /// <summary>Конструктор для граба диапазона страниц</summary>
        /// <param name="Site">Сайт</param>
        /// <param name="StartInd">начальный индекс страницы</param>
        /// <param name="Length">количество страниц</param>
        /// <param name="UseWebBrouser">Использовать компонент WebBrowser для загрузки страниц</param>
        public Grab(SITE Site, int StartInd, int Length, bool UseWebBrouser)
        {
            Construct(Site, StartInd, Length, null, UseWebBrouser);
        }

        /// <summary>Конструктор для граба указанных страниц</summary>
        /// <param name="Site">Сайт</param>
        /// <param name="AmvIdList">Список индексов страниц</param>
        /// <param name="UseWebBrouser">Использовать компонент WebBrowser для загрузки страниц</param>
        public Grab(SITE Site, List<int> AmvIdList, bool UseWebBrouser)
        {
            Construct(Site, 0, AmvIdList.Count, AmvIdList, UseWebBrouser);
        }

        void Construct(SITE Site, int StartInd, int Length, List<int> AmvIdList, bool UseWebBrouser)
        {
            tmr = new System.Windows.Forms.Timer();
            tmr.Tick += new System.EventHandler(tmr_Tick);
            tmr.Interval = 1;
            this.Site = Site;
            this.StartInd = StartInd;
            this.IndLength = Length;
            this.AmvIdList = AmvIdList;
            this.UseWebBrouser = UseWebBrouser;
            if (Length < 0) throw new Exception("Длина не может быть меньше нуля");
            if (StartInd < 0) throw new Exception("ID не может быть меньше нуля");
        }

        public void StartGrab()
        {
            // создание листа граба
            int listLen;
            int g1ListLen;
            int gLastListLen;
            if (IndLength < maxThriadsCount)
            {
                listLen = IndLength;
                g1ListLen = 1;
                gLastListLen = 1;
            }
            else
            {
                listLen = maxThriadsCount;
                g1ListLen = IndLength / maxThriadsCount;
                gLastListLen = IndLength - g1ListLen * (maxThriadsCount - 1);
            }

            #region *** заполнение листа листов для граба *********
            grabList = new List<GrabOneList>();
            int amvID = StartInd;
            int n = 0;
            for (int i = 0; i < listLen; i++)
            {
                GrabOneList grOneL = new GrabOneList(UseWebBrouser);
                int len = g1ListLen;
                if (i == (listLen - 1)) len = gLastListLen; // если последний лист
                for (int j = 0; j < len; j++)
                {
                    if (AmvIdList == null)
                    {
                        grOneL.grList.Add(new GrabInfo(Site, amvID));
                        amvID++;
                    }
                    else
                    {
                        grOneL.grList.Add(new GrabInfo(Site, AmvIdList[n]));
                        n++;
                    }
                }
                grOneL.DeleteGoodPadesAfterParse = DeleteGoodPadesAfterParse;
                grOneL.DeleteAllPadesAfterParse = DeleteAllPadesAfterParse;
                grabList.Add(grOneL);
            }
            #endregion *** заполнение листа листов для граба *********

            doneCount = 0;
            done = false;
            // старт
            tmr.Start();
        }

        public List<GrabInfo> GetGrabResult()
        {
            List<GrabInfo> ret = new List<GrabInfo>();
            foreach (GrabOneList grOne in grabList)
                foreach (GrabInfo gInf in grOne.grList)
                    ret.Add(gInf);
            return ret;
        }

        private void tmr_Tick(object sender, EventArgs e)
        {
            tmr.Stop();

            bool bDone = true;
            doneCount = 0;
            foreach (GrabOneList grOne in grabList)
            {
                doneCount += grOne.DoneCount;
                if (grOne.IsGrabAll) continue;
                bDone = false;
                grOne.Action();
            }
            if (!bDone) tmr.Start();
            else done = true;
        }

        ~Grab()
        {
            Dispose();
        }

        public void Dispose()
        {
            if (tmr != null)
            {
                tmr.Dispose();
                tmr = null;
            }
        }

    }
}
